package com.eii.resubmit.aop.request.advisor;

import com.eii.resubmit.aop.request.UAOP;
import com.eii.resubmit.aop.request.UServlet;
import com.eii.resubmit.aop.request.annotation.RepeatRedisLock;
import com.eii.resubmit.aop.request.checker.RedisLockHelper;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author eii
 * @time 2020/11/29 4:20 下午
 */
@Slf4j
@Component
public class RepeatRedisAdvisor implements PointcutAdvisor {
    //接口调用计数
    private static ConcurrentHashMap<String, AtomicInteger> countMap = new ConcurrentHashMap<>();
    /**
     * 阻塞型重复请求检查器
     */
    @Autowired
    private RedisLockHelper redisLockHelper;
    /**
     * 超时时间
     */
    private static final Integer TIMEOUT = 60000;
    /**
     * 默认单位:毫秒
     */
    public static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS;
    /**
     * 执行器
     * 保证TIMEOUT（60s）时间内，同一个ip，请求一个接一个的执行，并发执行将显示"error:重复请求"
     */
    private Advice advice = (MethodInterceptor) invocation -> {
        HttpServletRequest request = UServlet.request();
        String key = Util.redisServletKey(Util.fullName(invocation.getMethod()), request);
        AtomicInteger atomicInteger = countMap.computeIfAbsent(key, (k) -> new AtomicInteger(0));
        String value = UUID.randomUUID().toString();
        log.info("RepeatRedisAdvisor:{}, value:{}, 调用计数:{}", key, value, atomicInteger.addAndGet(1));
        try {
            boolean success = redisLockHelper.lock(key, value, TIMEOUT, TIME_UNIT);
            if (!success) {
                log.info("重复请求:IP地址{}", UServlet.getAddr(request));
                Map<String, String> resMap = new HashMap<>();
                resMap.put("result", "error:重复请求:IP地址[" + UServlet.getAddr(request) + "]");
                return resMap;
            }
            return invocation.proceed();
        } finally {
            redisLockHelper.unlock(key, value);
        }
    };

    private MethodMatcher methodMatcher = new MethodMatcher() {
        //step1： 这里对方法级别进行排除，不需要此advice的方法，可从这这里判断
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            log.info("两个参数的matches()，其结果true或false决定了advice是否执行");
            log.info("targetClass:{}, matchesMethod:{}", targetClass.getName(), method.getName());
            if (!UAOP.isController(targetClass)) {
                return false;
            }
            RepeatRedisLock annotation = RepeatRedisLock.RepeatRedisLockHelper.get(method, targetClass);
            if (annotation == null) {
                return false;
            }
            return true;
        }

        //step2：
        @Override
        public boolean isRuntime() {
            log.info("isRuntime 返回false，不需要进行三个参数的matches()的执行");
            return false;
        }

        //step3：
        @Override
        public boolean matches(Method method, Class<?> aClass, Object... objects) {
            log.info("matches 3 args");
            return false;
        }
    };

    private Pointcut pointcut = new Pointcut() {

        @Override
        public MethodMatcher getMethodMatcher() {
            return methodMatcher;
        }

        //这里对类进行级别排除，不需要此advice的类，可从这这里判断
        @Override
        public ClassFilter getClassFilter() {
            return clazz -> true;
        }

    };

    @Override
    public Pointcut getPointcut() {
        return pointcut;
    }

    @Override
    public Advice getAdvice() {
        return advice;
    }

    //step0：Spring还没有使用该属性 永远返回true了
    @Override
    public boolean isPerInstance() {
        return true;
    }
}
